﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using Rxx.Properties;

namespace Rxx.Parsers
{
	internal sealed class AnyParser<TSource, TResult> : IParser<TSource, TResult>
	{
		#region Public Properties
		public IParser<TSource, TSource> Next
		{
			get
			{
				if (selectedParser == null)
				{
					throw new InvalidOperationException(Errors.ParseNotCalledOrFailed);
				}

				return selectedParser.Next;
			}
		}

		public IEnumerable<IParser<TSource, TResult>> Parsers
		{
			get
			{
				Contract.Ensures(Contract.Result<IEnumerable<IParser<TSource, TResult>>>() != null);

				return parsers;
			}
		}
		#endregion

		#region Private / Protected
		private readonly IEnumerable<IParser<TSource, TResult>> parsers;
		private IParser<TSource, TResult> selectedParser;
		#endregion

		#region Constructors
		public AnyParser(IEnumerable<IParser<TSource, TResult>> parsers)
		{
			Contract.Requires(parsers != null);

			this.parsers = parsers;
		}
		#endregion

		#region Methods
		[ContractInvariantMethod]
		private void ObjectInvariant()
		{
			Contract.Invariant(parsers != null);
		}

		public IEnumerable<IParseResult<TResult>> Parse(ICursor<TSource> source)
		{
			bool hasResult = false;

			foreach (var parser in parsers)
			{
				foreach (var result in parser.Parse(source))
				{
					if (!hasResult)
					{
						hasResult = true;

						/* The XML lab has shown that Parse may be called multiple times on the same AnyParser 
						 * instance during a single Parse operation, sometimes with the same source but most of the time
						 * with a different source; therefore, the selected parser must be reassigned to the latest selection 
						 * for each call to Parse, maintaining a local variable (hasResult) to determine whether the current 
						 * call to Parse has matched while enumerating the choices.
						 * 
						 * It is currently unknown whether it is possible for a nested Parse operation to overwrite the 
						 * selected parser, or whether it will have any negative impact.
						 */
						selectedParser = parser;
					}

					yield return result;
				}

				if (hasResult)
				{
					yield break;
				}
			}

			// completing without results is failure
		}

		internal IEnumerable<IParseResult<TResult>> Parse(
			ICollection<IParser<TSource, TResult>> except,
			ICursor<TSource> source)
		{
			Contract.Requires(except != null);
			Contract.Requires(!except.IsReadOnly);
			Contract.Requires(source != null);
			Contract.Ensures(Contract.Result<IEnumerable<IParseResult<TResult>>>() != null);

			bool hasResult = false;

			foreach (var parser in parsers.Except(except))
			{
				foreach (var result in parser.Parse(source))
				{
					if (!hasResult)
					{
						hasResult = true;

						except.Add(parser);
					}

					yield return result;
				}

				if (hasResult)
				{
					yield break;
				}
			}

			// completing without results is failure
		}
		#endregion
	}
}